﻿using HslCommunication;
using HslCommunication.LogNet;
using HslCommunication.Profinet.LSIS;
using HslCommunication.Profinet.Siemens;
using LiveCharts;
using LiveCharts.Wpf;
using MiniExcelLibs;
using ScottPlot;
using ScottPlot.Plottable;
using Sunny.UI;
using SunnyScopplotS7netplus;
using SunnyUIScottPlotS7NetPlusWinform.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;

namespace SunnyUIScottPlotS7NetPlusWinform
{
    public partial class Form1 : UIForm
    {
        private DataGetModel _selectedGetModel;
        private List<DataGetModel> dataGetModels;
        private SiemensS7Net _s7Net;
        // 按照时间来实例化日志，指定按照小时存储
        private ILogNet logNet7 = new LogNetDateTime(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"), GenerateMode.ByEveryHour);

        double[] liveData = new double[400];
        DataGen.Electrocardiogram ecg = new DataGen.Electrocardiogram();
        Stopwatch sw = Stopwatch.StartNew();
        VLine vline;

        private CancellationTokenSource _ctsWrite = new CancellationTokenSource();
        private CancellationTokenSource _ctsDataCopy = new CancellationTokenSource();
        private CancellationTokenSource _ctsPlotRefresh = new CancellationTokenSource();

        private Task _taskWrite;
        private Task _taskDataCopy;
        private Task _taskPlotRefresh;


        private Thread thread;

        /**
         * 在 C# 中，当你在多个线程中访问同一个变量时：
         * 每个线程可能会把变量的值“缓存”在自己的内存里（为了提高速度），而不是每次都去主内存读取。
         * 所以即使你在主线程改了 _shouldStop = true，后台线程可能还在用它自己缓存的老值 false，导致循环不会退出。
         * 
        // 加上 volatile 的作用是任何线程读 _shouldStop 都会去内存拿最新值，写的时候也会立刻更新到内存。
        // volatile 的作用：
        // 确保变量在多线程之间读写时，总是访问主内存的最新值，不使用线程本地缓存。
        /**
         * 什么时候用？
         * 当你有一个变量：
         *   被多个线程访问
         *   用于控制程序流程（比如 _shouldStop = true）
         *   希望一个线程改了它，其他线程能立刻看到变化
         *   那就加上 volatile。
         */
        private volatile bool _shouldStop = false; // 停止线程标志

        private volatile bool _acqEnable = false;

        public Form1()
        {
            InitializeComponent();
            initData();
            InitDataCycleThread();
            InitPlots();
            //InitPlotDataThread();
            InitHeartThread();
        }


        //private void InitPlotDataThread()
        //{
        //    _taskDataCopy = Task.Run(() => UpdateDataTask(), _ctsDataCopy.Token);
        //    _taskPlotRefresh = Task.Run(() => PlotRefreshTask(), _ctsPlotRefresh.Token);
        //}

        private async Task PlotRefreshTask()
        {
            while (_acqEnable && !_ctsPlotRefresh.IsCancellationRequested)
            {
                Invoke(new Action(() =>
                {
                    //formsPlot1.Refresh();
                    formsPlot1.Render();
                }));

                await Task.Delay(100, _ctsPlotRefresh.Token);
            }
        }

        private async Task MockDataTask(CancellationToken ct)
        {
            while (!ct.IsCancellationRequested)
            {
                float nextValue = (float)ecg.GetVoltage(sw.Elapsed.TotalSeconds);

                if (_s7Net != null && s7Status.State == UILightState.On)
                {
                    if (_selectedGetModel != null)
                    {
                        await _s7Net.WriteAsync("DB1.DBD" + _selectedGetModel.Address.ToInt(), nextValue);
                    }
                }

                await Task.Delay(100, ct);
            }
        }

        int nextValueIndex = -1;
        private async Task UpdateDataTask()
        {
            while (_acqEnable && !_ctsDataCopy.IsCancellationRequested)
            {
                try
                {
                    // 从PLC里面读取
                    double nextValue = 0;

                    if (_s7Net != null && s7Status.State == UILightState.On)
                    {
                        OperateResult<float> operateResult = await _s7Net.ReadFloatAsync($"DB1.DBD{_selectedGetModel.Address.ToInt()}");
                        if (operateResult.IsSuccess)
                        {
                            nextValue = operateResult.Content;
                        }
                    }

                    // 更新ScottPlot控件曲线
                    if (uiCheckBoxRoll.Checked)
                    {
                        // "roll" new values over old values like a traditional ECG machine
                        nextValueIndex = (nextValueIndex < liveData.Length - 1) ? nextValueIndex + 1 : 0;
                        liveData[nextValueIndex] = nextValue;
                        vline.IsVisible = true;
                        vline.X = nextValueIndex;
                    }
                    else
                    {
                        // "scroll" the whole chart to the left
                        Array.Copy(liveData, 1, liveData, 0, liveData.Length - 1);
                        liveData[liveData.Length - 1] = nextValue;
                        vline.IsVisible = false;
                    }


                    // 更新LiveChat2控件曲线值
                    Invoke(new Action(() =>   // Ctrl+K+D  代码格式化
                    {
                        LineSeries series = (LineSeries)cartesianChart1.Series.First();
                        series.Values.Add(nextValue);
                        if (series.Values.Count > 100)
                        {
                            // 删除第一个值
                            series.Values.RemoveAt(0);
                        }
                        // 坐标轴的更新操作
                        cartesianChart1.Update();
                    }));

                    await Task.Delay(100, _ctsDataCopy.Token);
                }
                catch (Exception ex)
                {
                    logNet7.WriteError(ex.Message);
                }

            }

        }

        private void InitPlots()
        {
            // plot the data array only once and we can updates its values later
            formsPlot1.Plot.AddSignal(liveData);
            formsPlot1.Plot.AxisAutoX(margin: 0);
            formsPlot1.Plot.SetAxisLimits(yMin: -1, yMax: 2.5);

            // plot a red vertical line and save it so we can move it later
            vline = formsPlot1.Plot.AddVerticalLine(0, Color.Red, 2);

            // customize styling
            formsPlot1.Plot.Title("Electrocardiogram Strip Chart");
            formsPlot1.Plot.YLabel("Potential (mV)");
            formsPlot1.Plot.XLabel("Time (seconds)");
            formsPlot1.Plot.Grid(false);

            // 修改完所有设置后，调用一次 Render()
            // 避免报FormsPlot.Refresh() must be called after modifying the plot or its data”这类警告信息
            formsPlot1.Render();

            Closed += (sender, args) =>
            {
                _ctsDataCopy?.Cancel();
                _ctsPlotRefresh?.Cancel();

                // 主线程设置停止标志为true,通知线程可以退出
                _shouldStop = true;

                // 等待线程结束
                if (thread != null && thread.IsAlive)
                {
                    thread.Join();// 等待线程完成，可能会阻塞UI线程
                }
            };

            #region LiveChart2 实现实时曲线
            cartesianChart1.Series = new LiveCharts.SeriesCollection
            {
                new LineSeries{
                    Title="实时压力数据",
                    Values=new ChartValues<double>(), // 是double类型
                    PointGeometry=null,  // 不需要显示每一个点的标记 
                    
                }
            };

            // 配置X轴
            cartesianChart1.AxisX.Add(new Axis
            {
                // X轴显示样式
                LabelFormatter = val => DateTime.Now.ToString("hh:mm:ss"),
                Separator = new Separator { Step = 1 } // X轴的分隔间距(步长设置为1)
            });

            // 配置Y轴
            cartesianChart1.AxisY.Add(new Axis { Title = "压力值" });
            #endregion
        }


        /// <summary>
        /// 读取温度
        /// </summary>
        private void InitDataCycleThread()
        {
            thread = new Thread(() =>
            {
                while (!_shouldStop)
                {
                    try
                    {
                        if (_s7Net != null && s7Status.State == UILightState.On)
                        {
                            Invoke(new Action(async () =>
                            {
                                OperateResult<float> operateResult =
                                        await _s7Net.ReadFloatAsync($"DB1.DBD{_selectedGetModel.Address.ToInt()}");
                                if (operateResult.IsSuccess)
                                {
                                    uiTempture.Value = operateResult.Content;
                                    logNet7.WriteError("读取温度成功", operateResult.Content.ToString());
                                }
                            }));
                        }

                        Thread.Sleep(500);
                    }
                    catch (Exception ex)
                    {
                        logNet7.WriteError("读取温度失败", ex.Message);
                    }

                }
            });

            thread.IsBackground = true; // 设置后台线程,UI关闭后就会自动关闭
            thread.Start();
        }

        private void initData()
        {
            try
            {
                var iniFilePath = AppDomain.CurrentDomain.BaseDirectory + "config.ini";
                IniFile ini = new IniFile(iniFilePath);
                string dataGetPath = ini.ReadString("Setup",
                    "DataGetPath",
                     "DataGet.xlsx");
                string dataSetPath = ini.ReadString("Setup",
                    "DataSetPath",
                    "DataSet.xlsx");

                using (var stream = File.OpenRead(AppDomain.CurrentDomain.BaseDirectory + dataGetPath))
                {
                    dataGetModels = stream.Query<DataGetModel>().ToList();

                    gridPlc.DataSource = dataGetModels;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }


        }

        private async void uiTurnSwitch1_ValueChanged(object sender, bool value)
        {
            if (uiTurnSwitch1.Active)
            {
                try
                {
                    _s7Net = new SiemensS7Net(SiemensPLCS.S1200, s7Ip.Text);
                    _s7Net.Port = s7Port.Value;
                    _s7Net.Rack = (byte)s7Rack.Value;
                    _s7Net.Slot = (byte)s7Slot.Value;

                    logNet7.WriteInfo("PLC连接成功");

                    OperateResult<float> operateResult = await _s7Net.ReadFloatAsync("DB1.DBD" + _selectedGetModel.Address.ToInt());

                    if (operateResult.IsSuccess)
                    {
                        uiTempture.Value = operateResult.Content;
                    }

                }
                catch (Exception e)
                {
                    logNet7.WriteError(e.Message);
                }

            }
            else
            {
                s7Status.State = UILightState.Off;

                if (_s7Net != null)
                {
                    await _s7Net.ConnectCloseAsync();
                    _s7Net = null;
                }
            }
        }

        /// <summary>
        /// 心跳用于判断 PLC通讯是否正常
        /// </summary>
        /// <exception cref="NotImplementedException"></exception>
        private void InitHeartThread()
        {
            var thread = new Thread(async () =>
            {
                while (true)
                {
                    await Task.Delay(1000);

                    if (_s7Net != null)
                    {
                        var readResult = await _s7Net.ReadBoolAsync("M100");

                        s7Status.State = readResult.IsSuccess ? UILightState.On : UILightState.Off;
                    }
                    else
                    {
                        s7Status.State = UILightState.Off;
                    }
                }
            });

            thread.IsBackground = true; // 设置后台线程,UI关闭后就会自动关闭
            thread.Start();
        }


        private void uiCheckBoxRoll_CheckedChanged(object sender, EventArgs e)
        {
            // clear old data values
            for (int i = 0; i < liveData.Length; i++)
                liveData[i] = 0;
        }

        /// <summary>
        /// 采集数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uiButtonAcq_Click(object sender, EventArgs e)
        {
            if (_acqEnable)
            {
                sw.Stop();
                _acqEnable = false;
                _ctsDataCopy?.Cancel();
                _ctsPlotRefresh?.Cancel();
            }
            else
            {
                sw.Start();
                _acqEnable = true;
                _ctsDataCopy = new CancellationTokenSource();
                _ctsPlotRefresh = new CancellationTokenSource();

                _taskDataCopy = Task.Run(() => UpdateDataTask(), _ctsDataCopy.Token);
                _taskPlotRefresh = Task.Run(() => PlotRefreshTask(), _ctsPlotRefresh.Token);
            }
        }

        /// <summary>
        /// 模拟数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uiButtonMock_Click(object sender, EventArgs e)
        {
            // 先取消之前的任务
            _ctsWrite.Cancel();
            _ctsWrite = new CancellationTokenSource();
            _taskWrite = Task.Run(() => MockDataTask(_ctsWrite.Token), _ctsWrite.Token);
        }

        /// <summary>
        /// 获取选择的地址
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="index"></param>
        private void gridPlc_SelectIndexChange(object sender, int index)
        {
            if (gridPlc.SelectedRows.Count > 0)
            {
                _selectedGetModel = gridPlc.SelectedRows[0].DataBoundItem as DataGetModel;

                if (_selectedGetModel == null)
                {
                    UIMessageBox.Show("转换失败!");
                }
            }
            else
            {
                gridPlc.SelectedIndex = 0;
            }
        }

     
    }
}
